package com.ryan.ohos.extension.nested;

import com.ryan.ohos.extension.event.DispatchHelper;
import com.ryan.ohos.extension.event.impl.ViewGroupHelper;
import com.ryan.ohos.extension.event.interfaces.ViewGroup;
import com.ryan.ohos.extension.nested.component.NestedListContainer;
import com.ryan.ohos.extension.nested.component.NestedScrollView;
import com.ryan.ohos.extension.scroller.Scroller;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentParent;
import ohos.agp.components.VelocityDetector;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

/**
 * 滚动帮助类，借助此类可以重写原生控件的滚动并实现{@link NestedScrollingChild}接口，如ScrollView、ListContainer。
 * {@link NestedScrollView}与{@link NestedListContainer}使用了此类。
 *
 * 使用此类的控件需要实现{@link ScrollingView}接口
 * 转发控件的{@link ViewGroup}与{@link NestedScrollingChild}接口至此类的同名方法，并在快速滑动后调动{@link #fling(int, int)}
 */
public class ScrollHelper implements Runnable {
    public static final int TOUCH_SLOP = 2;
    public static final int MINIMUM_FLING_VELOCITY = 50;

    private final Component mView;
    private final ScrollingView mScrollingView;

    private final ViewGroupHelper impl;
    private final VelocityDetector mVelocityTracker;
    private final Scroller mScroller;
    private final EventHandler mHandler;

    private final NestedScrollingChildHelper mChildHelper;

    private int mLastMotionY, mLastMotionX;
    private int mLastScrollerY, mLastScrollerX;
    private int mNestedYOffset, mNestedXOffset;

    /**
     * Used during scrolling to retrieve the new offset within the window.
     */
    private final int[] mScrollOffset = new int[2];
    private final int[] mScrollConsumed = new int[2];

    public ScrollHelper(Component cmp) {
        mView = cmp;

        if (cmp instanceof ScrollingView) {
            mScrollingView = (ScrollingView) cmp;
            impl = new ViewGroupHelper(mScrollingView.getContainer());
            mVelocityTracker = VelocityDetector.obtainInstance();
            mScroller = new Scroller(cmp.getContext());
            mHandler = new EventHandler(EventRunner.getMainEventRunner());

            mChildHelper = new NestedScrollingChildHelper(mScrollingView.getContainer());
        } else {
            throw new RuntimeException("Components must implement ScrollingView");
        }

        setNestedScrollingEnabled(true);
    }

    public EventHandler getHandler() {
        return mHandler;
    }

    public int getLastMotionY() {
        return mLastMotionY;
    }

    public int getLastMotionX() {
        return mLastMotionX;
    }

    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        return DispatchHelper.dispatch(component, touchEvent);
    }

    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        impl.requestDisallowInterceptTouchEvent(disallowIntercept);
    }

    public boolean onInterceptTouchEvent(TouchEvent ev) {
        return true;
    }

    public boolean dispatchTouchEvent(TouchEvent ev) {
        return impl.dispatchTouchEvent(ev);
    }

    public boolean onTouchEvent(TouchEvent ev) {
        int x = (int) getX(ev);
        int y = (int) getY(ev);

        int action = ev.getAction();
        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN: {

                mNestedYOffset = 0;
                mVelocityTracker.clear();
                mVelocityTracker.addEvent(ev);

                if (!mScroller.isFinished()) {
                    abortAnimatedScroll();
                }

                mLastMotionX = x;
                mLastMotionY = y;

                startNestedScroll(mScrollingView.getDirection(), NestedScrollingHelper.TYPE_TOUCH);
                break;
            }
            case TouchEvent.POINT_MOVE: {
                mVelocityTracker.addEvent(ev);

                int deltaX = mLastMotionX - x;
                int deltaY = mLastMotionY - y;

                if (Math.abs(deltaX) < TOUCH_SLOP && Math.abs(deltaY) < TOUCH_SLOP) {
                    return true;
                }

                callRequestDisallowInterceptTouchEvent();

                if (dispatchNestedPreScroll(deltaX, deltaY, mScrollConsumed, mScrollOffset,
                        NestedScrollingHelper.TYPE_TOUCH)) {
                    deltaX -= mScrollConsumed[0];
                    deltaY -= mScrollConsumed[1];
                    mNestedXOffset += mScrollOffset[0];
                    mNestedYOffset += mScrollOffset[1];
                }

                mLastMotionX = x - mScrollOffset[0];
                mLastMotionY = y - mScrollOffset[1];

                final int oldX = getScrollX();
                final int oldY = getScrollY();

                mScrollingView.scrollByInternal(deltaX, deltaY);

                final int scrolledDeltaX = getScrollX() - oldX;
                final int scrolledDeltaY = getScrollY() - oldY;
                final int unconsumedX = deltaX - scrolledDeltaX;
                final int unconsumedY = deltaY - scrolledDeltaY;

                mScrollConsumed[0] = 0;
                mScrollConsumed[1] = 0;

                dispatchNestedScroll(scrolledDeltaX, scrolledDeltaY, unconsumedX, unconsumedY, mScrollOffset,
                        NestedScrollingHelper.TYPE_TOUCH, mScrollConsumed);

                mLastMotionX -= mScrollOffset[0];
                mNestedXOffset += mScrollOffset[0];

                mLastMotionY -= mScrollOffset[1];
                mNestedYOffset += mScrollOffset[1];
                break;
            }
            case TouchEvent.PRIMARY_POINT_UP: {
                mVelocityTracker.calculateCurrentVelocity(1000, 8000, 8000);
                int velocityX = (int) mVelocityTracker.getHorizontalVelocity();
                int velocityY = (int) mVelocityTracker.getVerticalVelocity();

                if ((Math.abs(velocityX) >= MINIMUM_FLING_VELOCITY) || (Math.abs(velocityY) >= MINIMUM_FLING_VELOCITY)) {
                    if (!dispatchNestedPreFling(velocityX, velocityY)) {
                        dispatchNestedFling(velocityX, velocityY, true);
                        fling((int) (velocityX * 1.5), (int) (velocityY * 1.5));
                    }
                }
//                doFling(-velocityX, -velocityY);
                endDrag();
                break;
            }
            case TouchEvent.CANCEL: {
                endDrag();
                break;
            }
//            case TouchEvent.OTHER_POINT_DOWN: {
//                break;
//            }
//            case TouchEvent.OTHER_POINT_UP: {
//                break;
//            }
        }
        return true;
    }

    public boolean isConsumed() {
        return impl.isConsumed();
    }

    public void fling(int velocityX, int velocityY) {
        if (mScrollingView.getContainer().getChildCount() > 0) {
            mScroller.fling(getScrollX(), getScrollY(), // start
                    velocityX, velocityY, // velocities
                    Integer.MIN_VALUE, Integer.MAX_VALUE, // x
                    Integer.MIN_VALUE, Integer.MAX_VALUE // y
            );
            runAnimatedScroll(true);
        }
    }

    @Override
    public void run() {
        if (mScroller.isFinished()) {
            return;
        }

        mScroller.computeScrollOffset();
        int x = mScroller.getCurrX();
        int y = mScroller.getCurrY();

        int unconsumedX = mLastScrollerX - x;
        int unconsumedY = mLastScrollerY - y;

        mLastScrollerX = x;
        mLastScrollerY = y;

        mScrollConsumed[0] = 0;
        mScrollConsumed[1] = 0;

        dispatchNestedPreScroll(unconsumedX, unconsumedY, mScrollConsumed, null,
                NestedScrollingHelper.TYPE_NON_TOUCH);
        unconsumedX -= mScrollConsumed[0];
        unconsumedY -= mScrollConsumed[1];

        if (unconsumedX != 0 || unconsumedY != 0) {
            final int oldScrollX = getScrollX();
            final int oldScrollY = getScrollY();

            mScrollingView.scrollByInternal(unconsumedX, unconsumedY);

            final int scrolledXByMe = getScrollX() - oldScrollX;
            final int scrolledYByMe = getScrollY() - oldScrollY;
            unconsumedX -= scrolledXByMe;
            unconsumedY -= scrolledYByMe;

            mScrollConsumed[0] = 0;
            mScrollConsumed[1] = 0;

            dispatchNestedScroll(scrolledXByMe, scrolledYByMe, unconsumedX, unconsumedY, mScrollOffset,
                    NestedScrollingHelper.TYPE_NON_TOUCH, mScrollConsumed);
            unconsumedX -= mScrollConsumed[0];
            unconsumedY -= mScrollConsumed[1];

        }

        if (!mScroller.isFinished()) {
            mHandler.postTask(this, 5);
        } else {
            stopNestedScroll(NestedScrollingHelper.TYPE_NON_TOUCH);
        }
    }

    // NestedScrollingChild

    public void setNestedScrollingEnabled(boolean enabled) {
        mChildHelper.setNestedScrollingEnabled(enabled);
    }

    public boolean isNestedScrollingEnabled() {
        return mChildHelper.isNestedScrollingEnabled();
    }

    public boolean startNestedScroll(int axes, int type) {
        return mChildHelper.startNestedScroll(axes, type);
    }

    public void stopNestedScroll(int type) {
        mChildHelper.stopNestedScroll(type);
    }

    public boolean hasNestedScrollingParent(int type) {
        return mChildHelper.hasNestedScrollingParent(type);
    }

    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow, int type, int[] consumed) {
        return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                offsetInWindow, type, consumed);
    }

    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) {
        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
    }

    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

    public float getY(TouchEvent e) {
        return e.getPointerScreenPosition(e.getIndex()).getY();
    }

    public float getX(TouchEvent e) {
        return e.getPointerScreenPosition(e.getIndex()).getX();
    }

    private void runAnimatedScroll(boolean participateInNestedScrolling) {
        if (participateInNestedScrolling) {
            startNestedScroll(mScrollingView.getDirection(), NestedScrollingHelper.TYPE_NON_TOUCH);
        } else {
            stopNestedScroll(NestedScrollingHelper.TYPE_NON_TOUCH);
        }
        mLastScrollerY = getScrollY();
        mLastScrollerX = getScrollX();
        mHandler.postTask(this, 5);
    }

    private void abortAnimatedScroll() {
        mScroller.abortAnimation();
        stopNestedScroll(NestedScrollingHelper.TYPE_NON_TOUCH);
    }

    private void endDrag() {
        mVelocityTracker.clear();
        stopNestedScroll(NestedScrollingHelper.TYPE_TOUCH);
    }

    private void callRequestDisallowInterceptTouchEvent() {
        if (!mScrollingView.disallowInterceptWhenMoving()) {
            return;
        }

        final ComponentParent parent = mView.getComponentParent();
        if (parent instanceof ViewGroup) {
            ((ViewGroup) parent).requestDisallowInterceptTouchEvent(true);
        }
    }

    private int getScrollX() {
        return mScrollingView.getScrollX();
    }

    private int getScrollY() {
        return mScrollingView.getScrollY();
    }
}
